#include "pca.h"
#include "representation.h"
#include "../covariance/qr.h"
#include <iostream>

#define ERROR 0.00000000001

PCA::PCA(){
}

PCA::PCA(std::vector<Data *> _datas) : datasX(_datas){
    n = datasX.size();
    multipleImages = true;

    Data *d = datasX.at(0);
    m_ = d->getWidth();
    n_ = d->getHeight();
    Matrix setX;
    std::cout << "entrou" << std::endl;

    if(n == 1){
        multipleImages = false;
        n = 2;

        //pegar os pontos que isso será a população
        Representation rep(datasX.at(0));
//        std::cout << "antes de representacao!" <<std::endl;
        std::vector<Point> points = rep.getAllPoints();
        population = points.size();

//        std::cout << "setando setX" << std::endl;
        setX = Matrix(population, n);//aqui será representando os pixeis da borda, por dois que é X e Y

        Point p;
        //Setando a matriz---------------------------------------------
        //coloca-se as posições dos pontos da borda
        for(int i = 0; i < population; i++){
            p = points.at(i);
            setX[i][0] = p.x;
            setX[i][1] = p.y;
        }

//        std::cout << "fez setX" << std::endl;


    }else{
        population = m_ * n_;

        setX = Matrix(population, n);//aqui os vetores X do livro estarão nas linhas, serão mxn vetores de n bandas


        //Setando a matriz---------------------------------------------
        Color color;
        int mxn_ = 0;
        //para cada coluna equivalente a linha do x1 (isso eh... o R)
        //preencher as linhas, isso é cada pixel da imagem
        for(int k = 0; k < n; k++){
            mxn_ = 0;
            d = datasX.at(k);
            for(int i = 0; i < n_; i++){
                for(int j = 0; j < m_; j++){
                    color = d->getData(j, i);
                    setX[mxn_++][k] = (double)color.red;
                }
            }
        }

    }

        std::cout << "aki!" <<std::endl;

//    std::cout << "Depois de povoar setX" <<std::endl;
    /* TESTE!
    setX = Matrix(4, 2);
    setX[0][0] = 1;   setX[0][1] = 1;
    setX[1][0] = 2;   setX[1][1] = 4;
    setX[2][0] = 4;   setX[2][1] = 2;
    setX[3][0] = 5;   setX[3][1] = 5;
    */

//    std::cout << "Antes de calcular a covariancia" <<std::endl;
    Matrix covSetX = setX.covarianceMatrix();
//    std::cout << "Depois de calcular a covariancia" <<std::endl;

    //Autovalores e Autovetores---------------------------------------------
//    std::cout << "Antes de QR" <<std::endl;
    QR qrMethod(covSetX, ERROR);
    qrMethod.calculate();

    Matrix lambda = Matrix(qrMethod.getLambda());
    a = Matrix(qrMethod.getVector());
//    std::cout << "Depois de QR" <<std::endl;
//
//    meanColumn = Matrix(1, n);
//    Matrix mean = setX.meanColumn(&meanColumn);
    meanColumn = setX.meanColumn();
    //mas so vou guardar uma linha

    setY = (setX - meanColumn) * (a);
//    setY.print();


    int mxn_;
    if(multipleImages){
        //Criando os bits da imagem feitas por Y---------------------------------------------
        Color color;
        double c;
        for(int k = 0; k < n; k++){
            mxn_ = 0;
            d = new Data(m_, n_);
            for(int i = 0; i < n_; i++){
                for(int j = 0; j < m_; j++){
                    c = setY[mxn_++][k];

                    color = Color(c, c, c);
                    d->setData(j, i, color);
                }
            }
            datasY.push_back(d);
        }
    }else{
        std::cout << "aki!" <<std::endl;
        double x, y, minX = setY[0][0], minY = setY[0][1], maxX = setY[0][0], maxY = setY[0][1];

        for(int i = 1 ; i < population; i++){
            x = setY[i][0];
            y = setY[i][1];
            if( x < minX ) minX = x;
            if( y < minY ) minY = y;
            if( x > maxX ) maxX = x;
            if( y > maxY ) maxY = y;
        }
//
        double lambda1 = lambda[0][0];
        double lambda2 = lambda[1][1];
        int pad = 10;

//            std::cout<< "minX: " <<minX << "\tminY: " << minY << "\tmaxX: " <<maxX << "\tmaxY: " << maxY << "\tlambda1: " << lambda1 << "\tlambda2: " << lambda2 <<std::endl;
        d = new Data(maxX - minX + (2*pad), maxY - minY + (2*pad));


        for(int i = 0 ; i < population; i++){
//            x = (setY[i][0] - minX + 3)*lambda1;//soma amsi 3 pq é o normalmente o que coloco de PAD
//            y = (setY[i][1] - minY + 3)*lambda2;
            x = (setY[i][0] - minX + pad);//soma amsi 3 pq é o normalmente o que coloco de PAD
            y = (setY[i][1] - minY + pad);

//            std::cout<< "x: " <<x << "\ty: " << y <<std::endl;

            d->setData((int)x, (int)y, Color(255., 255., 255.));
//            if((int)x < m_ && (int)y < n_)
//            else std::cout<< "ponto (" << (int)x << ", " <<(int)y <<") fora do quadro!" <<std::endl;
        }

        datasY.push_back(d);
    }
}

PCA::~PCA(){
}

Data *PCA::getData(int _type, int _image, int _lambda){
    //aqui só trata os typos 1, 2 (ja que o 0 e o original e a 3 usa a 2 e a original)
    switch(_type){
        case 1:{//mostrar o processo inverso com o conjunto Y
            return datasY.at(_image);
            break;
        }
        case 2:{
            //Criando os bits da imagem feitas por Y---------------------------------------------
            Data *d;
            int mxn_;
            double c;
            Color color;
            int k = _image;

            Matrix a_(n, n);//o resto das linhas fora o lambida serão zero deveria ser n x lambda
            for(int i = 0; i < n; i ++){
                for(int j = 0; j < _lambda; j++){
                    a_[i][j] = a[i][j];
                }
            }
            Matrix reconstructed = setY * a_.transpose() + meanColumn;//é para ser uma matrix mxn x n

            mxn_ = 0;
            d = new Data(m_, n_);
            for(int i = 0; i < n_; i++){
                for(int j = 0; j < m_; j++){
                    c = reconstructed[mxn_++][k];
                    color = Color(c, c, c);
                    d->setData(j, i, color);
                }
            }

            return d;
        }
    }
    return new Data;
}
